API GatewayをCloudFormationで構築したら、マネコンとの項目の関連がわからなすぎたので、二度と調べなくていいようにまとめました
CloudFormation、使ってますか?
同じ環境を何度も構築する時、マネジメントコンソールで手動構築するのはだるいので、よくCloudFormationを使って構築しています。
今回、API GatewayをCloudFormationで構築しようとしたのですが、マネジメントコンソールでメソッドリクエストや統合レスポンスまわりの入力とCloudFormationの項目の関連がわからなすぎて無駄に時間がかかったので、二度と調べなくていいようにまとめました。
非プロキシ統合のLambdaをリクエスト先にしたAPI GatewayのCloudFormationのテンプレートを、ブログの最後に載せました。 テンプレートだけ欲しい人は、最後だけ見てください。
想定する読者
CloudFormationの基本的な使い方や、記法については理解している前提です。
また、API Gatewayの全体的な構成や、リソースパスの設定、メソッドリクエスト(Method request)、統合リクエスト(Integration request)、統合レスポンス(Integration response)、メソッドレスポンス(Method response)にどういった値を入力するかは理解している前提で進めます。
このブログでは、API Gatewayのマネジメントコンソールで入力しているあの値、CloudFormationのどの項目に対応するんだっけなー。ってとこを知りたい方向けにまとめています。
API Gatewayの主なリソースタイプ
大きなくくりで言うと、API Gatewayのマネジメントコンソールのこの部分は、CloudFormationのこのリソースタイプに対応しています。
リソースタイプ | 説明 |
---|---|
AWS::ApiGateway::Resource | API Gatewayのリソースパスにあたる部分を設定 |
AWS::ApiGateway::Method | API GatewayのGETやPOSTのメソッドタイプにあわせて、それに対応するAWSサービスや前処理、後処理を設定 |
この、 AWS::ApiGateway::Resource
の話と、 AWS::ApiGateway::Method
のリソースタイプが結構ややこしいので、細かく見ていきます。
リソース(Resources)
リソースは、主に AWS::ApiGateway::Resource
で設定します。
リソースの詳細(Resource details)
リソースのパスを作成する画面とCloudFormationを比較します。
ApiResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref Api ## ↓ リソースを作成 - リソースの詳細 ParentId: !GetAtt Api.RootResourceId # リソースパス # ParentId: !Ref ApiResource PathPart: '{path-method-req}' #リソース名 ## ↑ リソースを作成 - リソースの詳細
マネジメントコンソールの リソースパス に当たる部分が、 ParentId
です。
マネジメントコンソールでは選択式ですが、CloudFormationではリソースIDを指定します。
ルートパス(/)の子リソースにしたい場合は、Api
を AWS::ApiGateway::RestApi
の論理IDとすると、 !GetAtt Api.RootResourceId
で指定できます。
その他のリソースの子リソースにしたい場合は、 !Ref ApiResource
みたいな形で指定できます。
- AWS::ApiGateway::Resource ParentId - AWS CloudFormation
- AWS::ApiGateway::RestApi Return values - AWS CloudFormation
- AWS::ApiGateway::Resource Return values - AWS CloudFormation
リソース名 に当たる部分が、 PathPart
です。
固有のパスではなく、パス変数を利用したい場合は、 {path-method-req}
のように {}(中括弧)
でパス変数名を囲みます。
ここに指定したパス変数を、メソッドリクエストのリクエストパラメータとして利用できます。
また、{path-method-req+}
のように +
をつけると、そのリソースより深いパス全てが対象になります。
API Gatewayでは、慣習的に {proxy+}
というパス変数名を利用することが多いです。
詳しくはこちらのドキュメントを御覧ください。
CORS(クロスオリジンリソース共有) のチェックボックスは、ちょっと特殊です。 CORS対応のためのOPTIONSメソッドを追加作成するかどうかを指定します。
このチェックボックスをチェックしておくと、OPTIONSメソッドが追加で作成されます。
CORS対応のためにどういったOPTIONSメソッドが作成されるかは、こちらのブログがわかりやすいのでご参照ください。
このチェックを入れてるときに、実際に作成されるOPTIONSメソッドをCloudFormationで表現したものが、こちらです。
## ↓ リソースを作成 - CORS(クロスオリジンリソース共有) ApiMethodOptions: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref ApiResource HttpMethod: OPTIONS AuthorizationType: NONE AuthorizationScopes: [] ApiKeyRequired: False Integration: Type: MOCK PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' IntegrationResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'" method.response.header.Access-Control-Allow-Origin: "'*'" MethodResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true ResponseModels: application/json: Empty ## ↑ リソースを作成 - CORS(クロスオリジンリソース共有)
メソッドリクエスト(Method request)
メソッドリクエストは、主に AWS::ApiGateway::Method
で設定します。
メソッドリクエストの設定(Method request settings)
メソッドリクエストを編集する画面とCloudFormationを比較します。
API Gatewayのメソッドリクエストの 認可 にあたる部分が、 AuthorizationType
です。
以下の設定値を必要に応じて指定します。
認可タイプ | 設定値 | 備考 |
---|---|---|
認可なし | NONE | ー |
IAM認可 | AWS_IAM | ー |
カスタム認可 | CUSTOM | 追加で AuthorizerId に AWS::ApiGateway::Authorizer で構築したID(e.g. !Ref Authorizer)を指定 |
Cognito認可 | COGNITO_USER_POOLS | 追加で AuthorizerId に AWS::ApiGateway::Authorizer で構築したリソースのID(e.g. !Ref Authorizer)を指定。 AuthorizationScopes に必要に応じてscopeのリストを指定。 |
- AWS::ApiGateway::Method AuthorizationType - AWS CloudFormation
- AWS::ApiGateway::Authorizer - AWS CloudFormation
リクエストバリデーター は、マネジメントコンソールとCloudFormationの設定が異なり、ちょっとややこしいです。
基本、マネジメントコンソールでは次の図のようにリクエストバリデーターは選択式になっています。
実は、選択して保存した際に、リクエストバリデーターが自動生成されています。
そのため、CloudFormationでリクエストバリデーターを設定する場合は、リクエストバリデーターのリソースを明示的に作成する必要があります。
CloudFormationでは、次のように AWS::ApiGateway::RequestValidator
でリクエストバリデータを作成して、 RequestValidatorId
にそのリソースのID(e.g. !Ref ParamsValidator)を指定します。
## ↓ メソッドリクエスト - リクエストバリデーター ParamsValidator: # クエリ文字列パラメータおよびヘッダーを検証 Type: AWS::ApiGateway::RequestValidator Properties: Name: params-only RestApiId: !Ref Api ValidateRequestBody: false ValidateRequestParameters: true BodyAndParamsValidator: # 本文、クエリ文字列パラメータ、およびヘッダーを検証 Type: AWS::ApiGateway::RequestValidator Properties: Name: body-and-params RestApiId: !Ref Api ValidateRequestBody: true ValidateRequestParameters: true BodyValidator: # 本文を検証 Type: AWS::ApiGateway::RequestValidator Properties: Name: body-only RestApiId: !Ref Api ValidateRequestBody: true ValidateRequestParameters: false ## ↑ メソッドリクエスト - リクエストバリデーター
- AWS::ApiGateway::Method RequestValidatorId - AWS CloudFormation
- AWS::ApiGateway::RequestValidator - AWS CloudFormation
APIキーは必須です のチェックボックスは、 ApiKeyRequired
で、 true
/ false
で指定します。
オペレーション名 は、 OperationName
で、任意の文字列で指定します。
リクエストパス、URLクエリ文字列パラメータ、HTTPリクエストヘッダー(Request paths, URL query string parameters, HTTP request headers)
リクエストパス 、 URLクエリ文字列パラメータ 、 HTTPリクエストヘッダー はちょっとややこしい構造になっています。
これらの必須かどうかは、 RequestParameters
で文字列オブジェクトとBool値で指定して、 キャッシュするかどうかは、 Integration
の CacheKeyParameters
で文字列リストで指定します。
具体的にCloudFormation化すると、こんな感じです。
ApiMethodAny: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref ApiResource HttpMethod: ANY ... RequestParameters: ## ↓ メソッドリクエスト - リクエストパス(パス変数と同じ変数) method.request.path.path-method-req: true ## ↑ メソッドリクエスト - リクエストパス(パス変数と同じ変数) ## ↓ メソッドリクエスト - URLクエリ文字列パラメータ method.request.querystring.query-method-req: true ## ↑ メソッドリクエスト - URLクエリ文字列パラメータ ## ↓ メソッドリクエスト - HTTP リクエストヘッダー method.request.header.header-method-req: true ## ↑ メソッドリクエスト - HTTP リクエストヘッダー ... Integration: ... CacheKeyParameters: ## ↓ メソッドリクエスト - リクエストパス(キャッシュ) - method.request.path.path-method-req ## ↑ メソッドリクエスト - リクエストパス(キャッシュ) ## ↓ メソッドリクエスト - URLクエリ文字列パラメータ(キャッシュ) - method.request.querystring.query-method-req ## ↑ メソッドリクエスト - URLクエリ文字列パラメータ(キャッシュ) ## ↓ メソッドリクエスト - HTTP リクエストヘッダー(キャッシュ) - method.request.header.header-method-req ## ↑ メソッドリクエスト - HTTP リクエストヘッダー(キャッシュ) ...
もう少し詳しく解説します。 リクエストパラメーターの設定について、CloudFormationのドキュメントを見るとこんな風に書いてあります。
RequestParameters
A key-value map defining required or optional method request parameters that can be accepted by API Gateway. A key is a method request parameter name matching the pattern of method.request.{location}.{name}, where location is querystring, path, or header and name is a valid and unique parameter name. The value associated with the key is a Boolean flag indicating whether the parameter is required (true) or optional (false). The method request parameter names defined here are available in Integration to be mapped to integration request parameters or templates.
つまり、 RequestParameters
でまとめて文字列オブジェクトとBool値で以下のように設定ができます。
設定項目 | 設定値 | 備考 |
---|---|---|
リクエストパス | method.request.path.{名前} | リソース作成時に設定したパス変数名と同じ名前で true を設定。マネジメントコンソールの場合、パス変数があれば自動生成される。 |
URLクエリ文字列パラメータ | method.request.querystring.{名前} | 必須かどうかで true / false を設定 |
HTTP リクエストヘッダー | method.request.header.{名前} | 必須かどうかで true / false を設定 |
一方、キャッシュを利用するかどうかは別の項目で指定します。
後々他のところでも出てきますが、キャッシュ関連は Integration
の CacheKeyParameters
で全部まとめて指定することになります。
こちらもCloudFormationのドキュメントを見るとこんな風に書いてあります。
CacheKeyParameters
A list of request parameters whose values API Gateway caches. To be valid values for cacheKeyParameters, these parameters must also be specified for Method requestParameters
正直、このドキュメントだけだとよくわからなかったんですよね…。 なので、実際にマネジメントコンソールから設定して、AWS CLIで現状の設定値を取得したりして調べました。
つまり、キャッシュしたい項目を下のような設定値で、 CacheKeyParametres
で文字列リストにして指定すればキャッシュが有効になります。
キャッシュしたい項目 | 設定値 |
---|---|
リクエストパス | method.request.path.{名前} |
URLクエリ文字列パラメータ | method.request.querystring.{名前} |
HTTP リクエストヘッダー | method.request.header.{名前} |
リクエスト本文(Request body)
リクエスト本文 に当たる部分は、 RequestModels
で指定します。
モデル自体はマネジメントコンソールと同様、別途作成が必要です。
CloudFormationでは、次のように AWS::ApiGateway::Model
でモデルを作成して、 RequestModels
にコンテンツタイプとあわせて、そのリソースのID(e.g. !Ref RequestBodyModel)を指定します。
RequestBodyModel: Type: AWS::ApiGateway::Model Properties: Name: RequestBodyModel RestApiId: !Ref Api ContentType: application/json Description: Request body model sample Schema: $schema: http://json-schema.org/draft-04/schema# title: RequestBodyModel type: object required: - body-method-req properties: body: type: string
- AWS::ApiGateway::Method RequestModels - AWS CloudFormation
- AWS::ApiGateway::Model - AWS CloudFormation
統合リクエスト(Integration request)
統合リクエストは、統合タイプによって設定が異なります。
今回は、 Lambda非プロキシ統合 を例に説明します。
統合リクエストの設定は、主に AWS::ApiGateway::Method
の Integration
で設定します。
Method details
統合リクエストを編集する画面とCloudFormationを比較します。
統合タイプ 周りの設定は、マネジメントコンソールとCloudFormationで結構違います。
マネジメントコンソールの方はラジオボタンで統合タイプを選択すると、自動で設定される部分が結構あります。 CloudFormationはその自動で設定されている部分を、明示的に設定する必要があります。
まず、 統合タイプに当たる部分は、 Type
なのですが、マネジメントコンソールとCloudFormationで選べる値が異なります。対応は次の通りです。
Type | マネジメントコンソール |
---|---|
AWS | Lambda関数(非プロキシ統合)、AWSのサービス |
AWS_PROXY | Lambda関数(プロキシ統合) |
HTTP | HTTP(非プロキシ統合)、VPCリンク |
HTTP_PROXY | HTTP(プロキシ統合) |
MOCK | Mock |
今回は、非プロキシ統合のLambda関数に限って、設定値を説明します。
Lambda関数 にあたる部分は、 Uri
で設定します。
マネジメントコンソールではリストから選択できますが、CloudFormationでは次のドキュメントに沿って、設定値を作る必要があります。
Uri
Specifies Uniform Resource Identifier (URI) of the integration endpoint.
For HTTP or HTTP_PROXY integrations, the URI must be a fully formed, encoded HTTP(S) URL according to the RFC-3986 specification for standard integrations. If connectionType is VPC_LINK specify the Network Load Balancer DNS name. For AWS or AWS_PROXY integrations, the URI is of the form arn:aws:apigateway:{region}:{subdomain.service|service}:path|action/{service_api}. Here, {Region} is the API Gateway region (e.g., us-east-1); {service} is the name of the integrated AWS service (e.g., s3); and {subdomain} is a designated subdomain supported by certain AWS service for fast host-name lookup. action can be used for an AWS service action-based API, using an Action={name}&{p1}={v1}&p2={v2}... query string. The ensuing {service_api} refers to a supported action {name} plus any required input parameters. Alternatively, path can be used for an AWS service path-based API. The ensuing service_api refers to the path to an AWS service resource, including the region of the integrated AWS service, if applicable. For example, for integration with the S3 API of GetObject, the uri can be either arn:aws:apigateway:us-west-2:s3:action/GetObject&Bucket={bucket}&Key={key} or arn:aws:apigateway:us-west-2:s3:path/{bucket}/{key}
ざっくり言うと、API Gatewayから実行させたい AWS API を指定しろということです。
API GatewayからLambdaを実行したいので、LambdaをInvokeするAPIを参照します。
リクエストの構文はこうですね。
POST /2015-03-31/functions/FunctionName/invocations?Qualifier=Qualifier HTTP/1.1 X-Amz-Invocation-Type: InvocationType X-Amz-Log-Type: LogType X-Amz-Client-Context: ClientContext
Payload
ここでいう FunctionName
は、実行したいLambdaのARNを指定します。
そして、ドキュメントに記載の通り値を組み合わせてUriを作っていくと、こんな形になります。
Uri: !Sub - arn:aws:apigateway:${Region}:${Service}:${ActionType}/${ServiceApi} - Region: !Sub ${AWS::Region} # AWSリージョン Service: lambda # AWS のサービス ActionType: path # アクションタイプ ServiceApi: !Sub 2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${Lambda}/invocations # アクション名/パスオーバーライド
なお、LambdaのInvoke APIは POST
で実行するので、 IntegrationHttpMethod
に POST
を指定しておきます。
基本、Lambdaはバイナリデータを受け取らないので、 ContentHandling
は CONVERT_TO_TEXT
を指定しておきます。実際、マネジメントコンソールでLambdaを選択したときは自動で設定されています。
これで、マネジメントコンソールで統合タイプにLambda関数を選択したときに自動で設定される部分を、CloudFormationで設定できます。
実行ロール に当たる部分は、 Credentials
にIAM RoleのARN文字列(e.g. !GetAtt ApiGatewayRole.Arn)で指定します。
- AWS::ApiGateway::Method Integration Credentials - AWS CloudFormation
- AWS::IAM::Role Return values - AWS CloudFormation
認証情報キャッシュ に当たる部分は、 CacheKeyParameters
で指定します。
先ほどメソッドリクエストでも出てきたやつですね。
このリストに、 caller.aws.account
を追加すると、 マネジメントコンソールで 発信者のアカウンドIDをキャッシュに追加する を選択したときと同じ挙動になります。
caller.aws.principal
を追加すると、 発信者のプリンシパルをキャッシュに追加する を選択したときと同じ挙動になります。
どちらも追加しない場合は、 発信者の認証情報をキャッシュキーに追加しない を選択したときと同じ挙動になります。
CloudFormationで具体的に設定すると、こんな感じです。
CacheKeyParameters: ## ↓ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のアカウンドIDをキャッシュに追加する) - caller.aws.account ## ↑ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のアカウンドIDをキャッシュに追加する) ## ↓ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のプリンシパルをキャッシュに追加する) # - caller.aws.principal ## ↑ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のプリンシパルをキャッシュに追加する)
カスタムタイムアウト は、 TimeoutInMillis
でミリ秒単位で指定します。
設定していない場合は、デフォルト値(29秒)が適用されます。
リクエスト本文のパススルー は、 PassthroughBehavior
で指定します。
設定値とマネジメントコンソールの表示の対応は次の通りです。
PassthroughBehavior | マネジメントコンソールの表示 |
---|---|
WHEN_NO_TEMPLATES | テンプレートが定義されていない場合 (推奨) |
WHEN_NO_MATCH | リクエストの content-type ヘッダーに一致するテンプレートがない場合 |
NEVER | 不可 |
URLパスパラメータ、URLクエリ文字列パラメータ、HTTPリクエストヘッダーのパラメータ(URL path parameters, URL query string parameters, URL request headers parameters)
これらのパラメータもメソッドリクエストと同様に、マッピング元の指定と、キャッシュの有無の指定を設定する項目が分かれています。
Integration
の RequestParameters
で指定し、 CacheKeyParameters
でキャッシュの有無を指定します。
具体的にCloudFormation化すると、こんな感じです。
ApiMethodAny: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref ApiResource HttpMethod: ANY ... Integration: RequestParameters: ## ↓ 統合リクエスト - URL パスパラメータ integration.request.path.path-integ-req: method.request.path.path-method-req ## ↑ 統合リクエスト - URL パスパラメータ ## ↓ 統合リクエスト - URL クエリ文字列パラメータ integration.request.querystring.query-integ-req: method.request.querystring.query-method-req ## ↑ 統合リクエスト - URL クエリ文字列パラメータ ## ↓ 統合リクエスト - HTTP ヘッダー integration.request.header.header-integ-req: method.request.header.header-method-req ## ↑ 統合リクエスト - HTTP ヘッダー CacheKeyParameters: ## ↓ 統合リクエスト - URL パスパラメータ(キャッシュ) - integration.request.path.path-integ-req ## ↑ 統合リクエスト - URL パスパラメータ(キャッシュ) ## ↓ 統合リクエスト - URL クエリ文字列パラメータ(キャッシュ) - integration.request.querystring.query-integ-req ## ↑ 統合リクエスト - URL クエリ文字列パラメータ(キャッシュ) ## ↓ 統合リクエスト - HTTP ヘッダー(キャッシュ) - integration.request.header.header-integ-req ## ↑ 統合リクエスト - HTTP ヘッダー(キャッシュ) ...
リクエストパラメーターの設定について、CloudFormationのドキュメントを見るとこんな風に書いてあります。
RequestParameters
A key-value map specifying request parameters that are passed from the method request to the back end. The key is an integration request parameter name and the associated value is a method request parameter value or static value that must be enclosed within single quotes and pre-encoded as required by the back end. The method request parameter value must match the pattern of method.request.{location}.{name}, where location is querystring, path, or header and name must be a valid and unique method request parameter name.
正直、このドキュメントだけだとよくわからなかったんですよね…。 なので、実際にマネジメントコンソールから設定して、AWS CLIで現状の設定値を取得したりして調べました。
次のように、integration.request.{設定項目}.{名前}
で各項目の設定ができるようです。
マッピング元は、メソッドリクエストのパラメータ名(method.request.{path, querystring or header}.{名前}
)を指定するか、一重引用符で静的な値(e.g. 'fixed-parameter')を指定できます。
設定項目 | 設定値 |
---|---|
URLパスパラメータ | integration.request.path.{名前} |
URLクエリ文字列パラメータ | integration.request.querystring.{名前} |
URLリクエストヘッダーのパラメータ | integration.request.header.{名前} |
今回の例ではメソッドリクエストで設定した各変数名を、次のように統合リクエストで変数名を変換するようにしています。
設定項目 | メソッドリクエストでの変数名 | 統合リクエストでの変数名 |
---|---|---|
URLパスパラメータ | path-method-req | path-integ-req |
URLクエリ文字列パラメータ | query-method-req | query-integ-req |
URLリクエストヘッダーのパラメータ | header-method-req | header-integ-req |
マッピングテンプレート
マッピングテンプレート は、 RequestTemplates
でコンテンツタイプと合わせて本文を指定します。
マッピングテンプレートの話は、1つのブログにできそうなくらいボリュームがあるので、本ブログではマッピングテンプレートの詳細には立ち入りません。マッピングテンプレートの内容については、理解している前提で進めます。
マッピングテンプレートの内容については、次のブログ等を参考にしてください。
- [AWS]API Gatewayの本文マッピングテンプレートを理解する | DevelopersIO
- API Gateway で統合レスポンスのマッピングテンプレートを使って特定条件時にリダイレクトさせる | DevelopersIO
今回の例では、次のドキュメントを参考に、全てのリクエストパラメーターをJSON形式で本文に埋め込むようなマッピングテンプレートを設定します。
RequestTemplates: application/json: |- #set($allParams = $input.params()) { "params" : { #foreach($type in $allParams.keySet()) #set($params = $allParams.get($type)) "$type" : { #foreach($paramName in $params.keySet()) "$paramName" : "$util.escapeJavaScript($params.get($paramName))" #if($foreach.hasNext),#end #end } #if($foreach.hasNext),#end #end }, "body": $input.body }
ここまでの設定がうまくいっていると、このAPI Gatewayをデプロイして、次のようなcurlコマンドを実行してパラメーターを渡した場合、マッピングテンプレートで変換されて、Lambda(AWS APIのLambda Invoke)のpayloadには、その次のようなJSON形式のリクエストが渡されるようになります。
$ export APIGATEWAY_URL=<<デプロイしたAPI GatewayのURL>> $ curl -X POST \ -d '{"body-method-req": "mybody"}' \ -H 'header-method-req: myheader' \ -H 'Content-Type: application/json' \ "${APIGATEWAY_URL}/mypath?query-method-req=myquery"
{ "params": { "path": { "path-method-req": "mypath" }, "querystring": { "query-method-req": "myquery" }, "header": { "header-method-req": "myheader" } }, "body": { "body-method-req": "mybody" } }
ちなみに、実際に渡されているJSONを見てみると、メソッドリクエストで設定したパラメーターが、マッピングテンプレートで変換されていることがわかります。
じゃあ、統合リクエストで変換したパラメーターはどこに行ったのかというと、Lambda(AWS APIのLambda Invoke)へクエリ文字列として渡されています。
今回で言えば、 query-integ-req=myquery
がクエリ文字列としてLambdaに渡されていますが、受け取らずに無視されます。
この辺の話は、こちらのブログが詳しいので、参考にしてください。
メソッドレスポンス(Method response)
メソッドレスポンスの設定は、主に AWS::ApiGateway::Method
の MethodResponses
で設定します。
レスポンスの詳細(Responses details)
メソッドレスポンスを作成する画面とCloudFormationを比較します。
HTTPステータスコード は StatusCode
で文字列で指定します。
ヘッダー名 は、 ResponseParameters
で method.response.header.{ヘッダー名}
の形式で、必須かどうかで true
/ false
とあわせて列挙します。
マネジメントコンソールでは必須かどうかのチェック項目がメソッドレスポンスに存在しませんが、後述の統合レスポンスのヘッダーのマッピングが設定されているかどうかで、内部的に必須かどうかの設定がされているようです。
レスポンス本文 に当たる部分は、 ResponseModels
で指定します。
モデル自体は、マネジメントコンソールと同様に、別途作成が必要です。
CloudFormationでは、次のように AWS::ApiGateway::Model
でモデルを作成して、 ResponseModels
にコンテンツタイプ(e.g. application/json)とあわせて、そのリソースのID(e.g. !Ref ResponseBodyModel)を指定します。
ResponseBodyModel: Type: AWS::ApiGateway::Model Properties: Name: ResponseBodyModel RestApiId: !Ref Api ContentType: application/json Description: Response body model sample Schema: $schema: http://json-schema.org/draft-04/schema# title: ResponseBodyModel type: object required: - body properties: body: type: object required: - body-method-res properties: body-method-res: type: string
- AWS::ApiGateway::Method MethodResponse ResponseModels - AWS CloudFormation
- AWS::ApiGateway::Model - AWS CloudFormation
統合レスポンス(Integration response)
統合レスポンスの設定は、主に AWS::ApiGateway::Method
の Integration
の IntegrationResponses
で設定します。
AWS::ApiGateway::Method Integration IntegrationResponses - AWS CloudFormation
レスポンスの詳細(Responses details)
統合レスポンスを編集する画面とCloudFormationを比較します。
HTTPステータスの正規表現 は、 SelectionPattern
で指定します。
設定しなかった場合は、デフォルトレスポンスとなります。
正規表現の設定方法については、次のドキュメントを参考にしてください。
メソッドレスポンスのステータスコード は、 StatusCode
で文字列で指定します。
このステータスコードは、メソッドレスポンスで作成したステータスコードと一致している必要があります。
コンテンツの処理 は、 ContentHandling
で指定します。
CloudFormationで設定する値と、マネジメントコンソールの表示の対応は次の通りです。
ContentHandling | マネジメントコンソールの表示 |
---|---|
CONVERT_TO_TEXT | テキストに変換 |
CONVERT_TO_BINARY | バイナリに変換 |
設定値無し | パススルー |
ヘッダーのマッピング(Header mapping)
ヘッダーのマッピング は、 ResponseParameters
で method.response.header.{ヘッダー名}
の形式で、マッピングしたい値とあわせて列挙します。
マッピングしたい値は、Lambda(AWS APIのLambda Invoke)からリターンされたHTTPリクエストのヘッダー(e.g. integration.response.header.{名前})や、リクエストボディからJSON式(e.g. integration.response.body.{JSON-expression})で設定するか、一重引用符で静的な値(e.g. 'fixed-parameter')を指定できます。
今回の例では、LambdaからリターンされたHTTPリクエストのボディから、params.header.header-method-req
(リクエスト時にheader-method-req
ヘッダーに設定していて、統合リクエストのマッピングテンプレートでボディにマッピングされた値)を取得して、ヘッダー名 header-method-res
に再マッピングしています。
ResponseParameters: method.response.header.header-method-res: integration.response.body.params.header.header-method-req
マッピングテンプレート(Mapping template)
マッピングテンプレート は、 ResponseTemplates
でコンテンツタイプと合わせて本文を指定します。
今回の例では、次のドキュメントを参考に、リクエスト本文に含まれていた body-method-req
パラメータ名を、 body-method-res
に変換するマッピングテンプレートにしています。
ResponseTemplates: application/json: |- { "params": $input.json('$.params'), "body": {"body-method-res": $input.json('$.body.body-method-req')} }
ここまでの設定がうまくいっていると、このAPI Gatewayをデプロイして、次のようなcurlコマンドを実行してパラメーターを渡した場合、マッピングテンプレートで変換されて、Lambdaに渡り、Lambdaは受け取ったpayloadをそのままリターンするようにしておきます。 そして、レスポンスデータもマッピングテンプレートで変換されて、その次のようなJSON形式の結果が返ってきます。
$ export APIGATEWAY_URL=<<デプロイしたAPI GatewayのURL>> $ curl -i -X POST \ -d '{"body-method-req": "mybody"}' \ -H 'header-method-req: myheader' \ -H 'Content-Type: application/json' \ "${APIGATEWAY_URL}/mypath?query-method-req=myquery"
HTTP/2 200 date: Wed, 01 May 2024 08:27:24 GMT content-type: application/json content-length: 463 x-amzn-requestid: 4948b355-a8b4-4719-ad07-46b162187abe x-amz-apigw-id: XFR04GwqNjMEf3Q= header-method-res: myheader x-amzn-trace-id: Root=1-6631fceb-45a39f755e635be24dc7b2ee;Parent=19b0bf74599fa6ba;Sampled=0;lineage=c0b71b4a:0
{ "params": { "path": { "path-method-req": "mypath" }, "querystring": { "query-method-req": "myquery" }, "header": { "accept": "*/*", "content-type": "application/json", "header-method-req": "myheader", "Host": "xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com", "User-Agent": "curl/8.4.0", "X-Amzn-Trace-Id": "Root=1-6631cf77-323a12f90d7abdd762c86dc3", "X-Forwarded-For": "xx.xx.xx.xx", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" } }, "body": { "body-method-res": "mybody" } }
返却されたヘッダー header-method-res
には、リクエストの header-method-req
ヘッダーに設定した値が入っており、ちゃんとマッピングされて返ってきてるなーということがわかります。
おわりに
API Gatewayのメソッド周りについてCloudFormationで設定する方法をまとめました。
マネジメントコンソールと構造が結構違って、いろいろなドキュメントを往復したのでもう二度と調べたくないです。そういう気持ちで書きました。 次やる時は、このブログを見返してやります。
このブログが、他の方の参考になれば幸いです。
おまけ(CloudFormationコード全体)
AWSTemplateFormatVersion: "2010-09-09" Parameters: StageName: Description: Name of API stage. Type: String Default: prod Resources: LambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Path: / ApiGatewayRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AWSLambda_FullAccess Path: / Lambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: | exports.handler = async function(event) { return event } FunctionName: api-lambda MemorySize: 128 Runtime: nodejs20.x Handler: index.handler Role: !GetAtt LambdaRole.Arn Api: Type: AWS::ApiGateway::RestApi Properties: Name: SampleApi EndpointConfiguration: Types: - REGIONAL RequestBodyModel: Type: AWS::ApiGateway::Model Properties: Name: RequestBodyModel RestApiId: !Ref Api ContentType: application/json Description: Request body model sample Schema: $schema: http://json-schema.org/draft-04/schema# title: RequestBodyModel type: object required: - body-method-req properties: body-method-req: type: string ResponseBodyModel: Type: AWS::ApiGateway::Model Properties: Name: ResponseBodyModel RestApiId: !Ref Api ContentType: application/json Description: Response body model sample Schema: $schema: http://json-schema.org/draft-04/schema# title: ResponseBodyModel type: object required: - body properties: body: type: object required: - body-method-res properties: body-method-res: type: string ## ↓ メソッドリクエスト - リクエストバリデーター ParamsValidator: # クエリ文字列パラメータおよびヘッダーを検証 Type: AWS::ApiGateway::RequestValidator Properties: Name: params-only RestApiId: !Ref Api ValidateRequestBody: false ValidateRequestParameters: true BodyAndParamsValidator: # 本文、クエリ文字列パラメータ、およびヘッダーを検証 Type: AWS::ApiGateway::RequestValidator Properties: Name: body-and-params RestApiId: !Ref Api ValidateRequestBody: true ValidateRequestParameters: true BodyValidator: # 本文を検証 Type: AWS::ApiGateway::RequestValidator Properties: Name: body-only RestApiId: !Ref Api ValidateRequestBody: true ValidateRequestParameters: false ## ↑ メソッドリクエスト - リクエストバリデーター ApiResource: Type: AWS::ApiGateway::Resource Properties: RestApiId: !Ref Api ## ↓ リソースを作成 - リソースの詳細 ParentId: !GetAtt Api.RootResourceId # リソースパス # ParentId: !Ref ApiResource PathPart: '{path-method-req}' #リソース名 ## ↑ リソースを作成 - リソースの詳細 ApiMethodAny: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref ApiResource HttpMethod: ANY ## ↓ メソッドリクエスト - メソッドリクエストの設定 AuthorizationType: NONE # 認可 AuthorizerId: !Ref AWS::NoValue AuthorizationScopes: [] # Authorization Scopes RequestValidatorId: !Ref BodyAndParamsValidator # リクエストバリデーター ApiKeyRequired: false # API キーは必須です OperationName: AnyMethodOperation # オペレーション名 - オプション ## ↑ メソッドリクエスト - メソッドリクエストの設定 RequestParameters: ## ↓ メソッドリクエスト - リクエストパス(パス変数と同じ変数) method.request.path.path-method-req: true ## ↑ メソッドリクエスト - リクエストパス(パス変数と同じ変数) ## ↓ メソッドリクエスト - URLクエリ文字列パラメータ method.request.querystring.query-method-req: true ## ↑ メソッドリクエスト - URLクエリ文字列パラメータ ## ↓ メソッドリクエスト - HTTP リクエストヘッダー method.request.header.header-method-req: true ## ↑ メソッドリクエスト - HTTP リクエストヘッダー ## ↓ メソッドリクエスト - リクエスト本文 RequestModels: application/json: !Ref RequestBodyModel ## ↑ メソッドリクエスト - リクエスト本文 Integration: ## ↓ 統合リクエスト - Method details Type: AWS # 統合タイプ Uri: !Sub - arn:aws:apigateway:${Region}:${Service}:${ActionType}/${ServiceApi} - Region: !Sub ${AWS::Region} # AWSリージョン Service: lambda # AWS のサービス ActionType: path # アクションタイプ ServiceApi: !Sub 2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${Lambda}/invocations # アクション名/パスオーバーライド IntegrationHttpMethod: POST # HTTPメソッド ContentHandling: CONVERT_TO_TEXT # コンテンツの処理 Credentials: !GetAtt ApiGatewayRole.Arn # 実行ロール TimeoutInMillis: 28000 # カスタムタイムアウト PassthroughBehavior: WHEN_NO_TEMPLATES # リクエスト本文のパススルー ## ↑ 統合リクエスト - Method details RequestParameters: ## ↓ 統合リクエスト - URL パスパラメータ integration.request.path.path-integ-req: method.request.path.path-method-req ## ↑ 統合リクエスト - URL パスパラメータ ## ↓ 統合リクエスト - URL クエリ文字列パラメータ integration.request.querystring.query-integ-req: method.request.querystring.query-method-req ## ↑ 統合リクエスト - URL クエリ文字列パラメータ ## ↓ 統合リクエスト - HTTP ヘッダー integration.request.header.header-integ-req: method.request.header.header-method-req ## ↑ 統合リクエスト - HTTP ヘッダー CacheKeyParameters: ## ↓ メソッドリクエスト - リクエストパス(キャッシュ) - method.request.path.path-method-req ## ↑ メソッドリクエスト - リクエストパス(キャッシュ) ## ↓ メソッドリクエスト - URLクエリ文字列パラメータ(キャッシュ) - method.request.querystring.query-method-req ## ↑ メソッドリクエスト - URLクエリ文字列パラメータ(キャッシュ) ## ↓ メソッドリクエスト - HTTP リクエストヘッダー(キャッシュ) - method.request.header.header-method-req ## ↑ メソッドリクエスト - HTTP リクエストヘッダー(キャッシュ) ## ↓ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のアカウンドIDをキャッシュに追加する) - caller.aws.account ## ↑ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のアカウンドIDをキャッシュに追加する) ## ↓ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のプリンシパルをキャッシュに追加する) # - caller.aws.principal ## ↑ 統合リクエスト - Method details(認証情報キャッシュ - 発信者のプリンシパルをキャッシュに追加する) ## ↓ 統合リクエスト - URL パスパラメータ(キャッシュ) - integration.request.path.path-integ-req ## ↑ 統合リクエスト - URL パスパラメータ(キャッシュ) ## ↓ 統合リクエスト - URL クエリ文字列パラメータ(キャッシュ) - integration.request.querystring.query-integ-req ## ↑ 統合リクエスト - URL クエリ文字列パラメータ(キャッシュ) ## ↓ 統合リクエスト - HTTP ヘッダー(キャッシュ) - integration.request.header.header-integ-req ## ↑ 統合リクエスト - HTTP ヘッダー(キャッシュ) ## ↓ 統合リクエスト - マッピングテンプレート RequestTemplates: application/json: |- #set($allParams = $input.params()) { "params" : { #foreach($type in $allParams.keySet()) #set($params = $allParams.get($type)) "$type" : { #foreach($paramName in $params.keySet()) "$paramName" : "$util.escapeJavaScript($params.get($paramName))" #if($foreach.hasNext),#end #end } #if($foreach.hasNext),#end #end }, "body": $input.body } ## ↑ 統合リクエスト - マッピングテンプレート IntegrationResponses: ## ↓ 統合レスポンス - レスポンスの詳細 - SelectionPattern: .* # Lambda エラーの正規表現 StatusCode: '200' # メソッドレスポンスのステータスコード ContentHandling: CONVERT_TO_TEXT # コンテンツの処理 ## ↑ 統合レスポンス - レスポンスの詳細 ## ↓ 統合レスポンス - ヘッダーのマッピング ResponseParameters: method.response.header.header-method-res: integration.response.body.params.header.header-method-req ## ↑ 統合レスポンス - ヘッダーのマッピング ## ↓ 統合レスポンス - マッピングテンプレート ResponseTemplates: application/json: |- { "params": $input.json('$.params'), "body": {"body-method-res": $input.json('$.body.body-method-req')} } ## ↑ 統合レスポンス - マッピングテンプレート MethodResponses: ## ↓ メソッドレスポンス - レスポンスの詳細 - StatusCode: '200' # HTTP ステータスコード ResponseParameters: # ヘッダー名 method.response.header.header-method-res: true ResponseModels: # レスポンス本文 application/json: !Ref ResponseBodyModel ## ↑ メソッドレスポンス - レスポンスの詳細 ## ↓ リソースを作成 - CORS(クロスオリジンリソース共有) ApiMethodOptions: Type: AWS::ApiGateway::Method Properties: RestApiId: !Ref Api ResourceId: !Ref ApiResource HttpMethod: OPTIONS AuthorizationType: NONE AuthorizationScopes: [] ApiKeyRequired: False Integration: Type: MOCK PassthroughBehavior: WHEN_NO_MATCH RequestTemplates: application/json: '{"statusCode": 200}' IntegrationResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'" method.response.header.Access-Control-Allow-Origin: "'*'" MethodResponses: - StatusCode: '200' ResponseParameters: method.response.header.Access-Control-Allow-Headers: true method.response.header.Access-Control-Allow-Methods: true method.response.header.Access-Control-Allow-Origin: true ResponseModels: application/json: Empty ## ↑ リソースを作成 - CORS(クロスオリジンリソース共有) ApiDeployment: Type: AWS::ApiGateway::Deployment DependsOn: - ApiMethodAny - ApiMethodOptions - RequestBodyModel - ResponseBodyModel Properties: RestApiId: !Ref Api StageName: !Sub ${StageName} Outputs: ApiRootUrl: Description: Root Url of the API Value: !Sub https://${Api}.execute-api.${AWS::Region}.amazonaws.com/${StageName}